package com.sunricher.telinkblemeshlib;

import android.os.Handler;
import android.util.Log;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class MeshDeviceStatusNotifier {

    private static final String LOG_TAG = "MeshDeviceStatusNotifier";

    private MeshDeviceStatusNotifier() {
        Log.i(LOG_TAG, "mesh device status notifier created");
    }

    public static MeshDeviceStatusNotifier getInstance() {
        return MeshDeviceStatusNotifierHolder.instance;
    }

    private static class MeshDeviceStatusNotifierHolder {
        private static final MeshDeviceStatusNotifier instance = new MeshDeviceStatusNotifier();
    }

    private ConcurrentMap<Integer, MeshDeviceCache> offlineDeviceMap = new ConcurrentHashMap<>();
    private HashSet<Integer> deviceSet = new HashSet<>();

    private NotifierCallback callback;
    private long delaySeconds = 0;
    private final Handler handler = new Handler();
    private Runnable runnable;

    public void setDelaySeconds(long delaySeconds) {

        if (runnable != null) {

            handler.removeCallbacks(runnable);
        }

        this.delaySeconds = delaySeconds;
        if (!isDelayEnabled()) {

            return;
        }

        runnable = new Runnable() {
            @Override
            public void run() {

                Iterator<MeshDeviceCache> iterator = offlineDeviceMap.values().iterator();
                while (iterator.hasNext()) {

                    MeshDeviceCache cache = iterator.next();
                    if (cache != null) {

                        notifyDeviceCache(cache);
                    }
                }

                handler.postDelayed(this, 1000);
            }
        };

        // Run runnable every second.
        handler.postDelayed(runnable, 1000);
    }

    void addMeshDevices(ArrayList<MeshDevice> devices) {

        ArrayList<MeshDevice> notiDevices = new ArrayList<>();

        for (MeshDevice device : devices) {

            // If is delay enabled and offline, we delay it.
            if (isDelayEnabled() && device.getState() == MeshDevice.State.offline) {

                if (deviceSet.contains(device.getAddress())) {

                    // It's an old device, we need to delay it.
                    offlineDeviceMap.put(device.getAddress(), new MeshDeviceCache(device));

                } else {

                    // It's a new device, we need to notify immediately.
                    notiDevices.add(device);
                }

            } else {

                notiDevices.add(device);
                offlineDeviceMap.remove(device.getAddress());
            }

            deviceSet.add(device.getAddress());
        }

        if (callback != null && notiDevices.size() > 0) {

            callback.didUpdateMeshDevices(notiDevices);
        }
    }

    void clear() {

        deviceSet.clear();
        offlineDeviceMap.clear();
    }

    void setCallback(NotifierCallback callback) {

        this.callback = callback;
    }

    private boolean isDelayEnabled() {

        return delaySeconds >= 1;
    }

    private void notifyDeviceCache(MeshDeviceCache cache) {

        long diff = (new Date().getTime() - cache.created) - (delaySeconds * 1000);
        boolean isActivate = diff >= 0;

        if (isActivate) {

            if (callback != null) {

                ArrayList<MeshDevice> meshDevices = new ArrayList<>();
                meshDevices.add(cache.meshDevice);
                callback.didUpdateMeshDevices(meshDevices);
            }

            offlineDeviceMap.remove(cache.meshDevice.getAddress());
        }
    }

    private static class MeshDeviceCache {

        private MeshDevice meshDevice;
        private long created;

        private MeshDeviceCache(MeshDevice device) {

            this.meshDevice = device;
            this.created = new Date().getTime();
        }
    }

    public abstract static class NotifierCallback {

        public abstract void didUpdateMeshDevices(ArrayList<MeshDevice> devices);
    }
}
